package com.coincalf.exchange.service.impl;

import com.coincalf.exchange.entity.*;
import com.coincalf.exchange.service.AccountService;
import com.coincalf.exchange.service.EntrustOrderService;
import com.coincalf.exchange.service.TurnoverOrderService;
import com.coincalf.framework.constants.Constant;
import com.coincalf.framework.enums.OrderStatus;
import com.coincalf.framework.enums.OrderType;
import com.coincalf.framework.exception.TradeMatchException;
import com.coincalf.framework.exception.VolumeException;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 撮合服务
 * @Author: Chen Long
 * @Date: Created in 2018/5/13 下午2:37
 * @Modified by: Chen Long
 */
@Slf4j
@Service
public class MatchService implements Constant {

    @Autowired
    private TurnoverOrderService turnoverOrderService;
    @Autowired
    private EntrustOrderService entrustOrderService;
    @Autowired
    private AccountService accountService;

    /**
     * 撮合买卖单
     *
     * @param entrustOrder 待撮合委托单
     * @param matchOrder   匹配委托单
     * @param market       当前待成交量
     * @param orderType    委托单类型
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> tradeMatch(
            EntrustOrder entrustOrder,
            EntrustOrder matchOrder,
            Market market,
            OrderType orderType
    ) throws VolumeException, TradeMatchException {
        // 买单
        EntrustOrder buyOrder = entrustOrder.getType() == OrderType.BUY.getCode() ? entrustOrder : matchOrder;
        // 卖单
        EntrustOrder sellOrder = entrustOrder.getType() == OrderType.BUY.getCode() ? matchOrder : entrustOrder;
        // 校验资金账户
        Account buyAccount = accountService.queryByUserIdAndCoinId(buyOrder.getUserId(), market.getBuyCoinId());
        if (buyAccount == null) {
            throw new TradeMatchException("撮合失败：买单用户资金账户异常");
        }
        // 校验资金账户
        Account sellAccount = accountService.queryByUserIdAndCoinId(sellOrder.getUserId(), market.getSellCoinId());
        if (sellAccount == null) {
            throw new TradeMatchException("撮合失败：卖单用户资金账户异常");
        }
        // 获取系统当前时间
        Date currentTime = new DateTime().toDate();
        // 可以买的数量 = 委托数量 - 已成交数量
        BigDecimal buyVolume = buyOrder.getVolume().subtract(buyOrder.getDeal());
        // 可卖数量 = 委托数量 - 已成交数量
        BigDecimal sellVolume = sellOrder.getVolume().subtract(sellOrder.getDeal());
        if (buyVolume.compareTo(BigDecimal.ZERO) <= 0 || sellVolume.compareTo(BigDecimal.ZERO) <= 0) {
            throw new VolumeException("撮合失败：成交量异常");
        }
        // 本次撮合成交数量（可卖量和可买量取小）
        BigDecimal volume = buyVolume.min(sellVolume).equals(buyVolume) ? buyVolume : sellVolume;
        // 成交价 = 买单取卖价，卖单取买价
        BigDecimal price = orderType == OrderType.BUY ? sellOrder.getPrice() : buyOrder.getPrice();
        // 成交金额 = 成交量 * 成交价
        BigDecimal amount = volume.multiply(price).setScale(8, BigDecimal.ROUND_HALF_UP);
        // 买入手续费 = 成交金额 * 买入手续费比例
        BigDecimal buyFee = amount.multiply(buyOrder.getFeeRate()).setScale(8, BigDecimal.ROUND_HALF_UP);
        // 卖出手续费 = 成交金额 * 交易市场配置的卖出手续费率
        BigDecimal sellerFee = amount.multiply(sellOrder.getFeeRate()).setScale(8, BigDecimal.ROUND_HALF_UP);
        // 新的买入冻结金额 = 买入价 * （委托买入量 - 已成交量 - 本次成交量）
        BigDecimal newBuyLockAmount = buyOrder.getPrice()
                .multiply(buyOrder.getVolume()
                        .subtract(buyOrder.getDeal())
                        .subtract(volume)).setScale(8, BigDecimal.ROUND_HALF_UP);
        //TODO 自己挂的买单和自己挂的卖单不能成交
        //if (buyOrder.getUserId().equals(sellOrder.getUserId())) {
        //    throw new TradeMatchException("币币交易买家和买家不能相同");
        //}
        // 成交订单
        TurnoverOrder turnoverOrder = new TurnoverOrder();
        turnoverOrder.setOrderId(entrustOrder.getId())          // 订单号
                .setMarketId(entrustOrder.getMarketId())        // 交易市场ID
                .setSymbol(entrustOrder.getSymbol())            // 交易市场标识符
                .setMarketName(entrustOrder.getMarketName())    // 交易对名称
                .setMarketType(entrustOrder.getMarketType())    // 交易对类型
                .setTradeType(orderType.getCode())              // 撮合成交类型
                .setBuyOrderId(buyOrder.getId())                // 委托买单ID
                .setBuyUserId(buyOrder.getUserId())             // 买单用户ID
                .setBuyVolume(buyOrder.getVolume())             // 委托买单数量
                .setBuyPrice(buyOrder.getPrice())               // 委托买入价
                .setBuyFeeRate(buyOrder.getFeeRate())           // 买入手续费费率
                .setBuyCoinId(market.getBuyCoinId())            // 买入支付币种
                .setDealBuyFee(buyFee)                          // 买入手续费
                .setSellOrderId(sellOrder.getId())              // 委托卖单ID
                .setSellUserId(sellOrder.getUserId())           // 卖单用户ID
                .setSellVolume(sellOrder.getVolume())           // 委托卖单数量
                .setSellPrice(sellOrder.getPrice())             // 委托卖出价
                .setSellFeeRate(sellOrder.getFeeRate())         // 卖出手续费费率
                .setSellCoinId(market.getSellCoinId())          // 买出币种
                .setDealSellFee(sellerFee)                      // 卖出手续费
                .setAmount(amount)                              // 成交金额
                .setDealBuyFeeRate(buyOrder.getFeeRate())       // 成交买单手续费率
                .setVolume(volume)                              // 成交数量
                .setPrice(price)                                // 成交价格
                .setDealSellFeeRate(sellOrder.getFeeRate())     // 成交卖出手续费率
                .setStatus(OrderStatus.DEAL.getCode())          // 成交状态
                .setCreated(currentTime)                        // 交易时间
                .setLastUpdateTime(currentTime);                // 修改时间
        // 创建成交单记录
        turnoverOrderService.insert(turnoverOrder);


        // 新的买入冻结金额 = 新的买入冻结金额 + (newBuyLockAmount * 买入手续费比例)
        newBuyLockAmount = newBuyLockAmount.add(newBuyLockAmount.multiply(buyOrder.getFeeRate())).setScale(8, BigDecimal.ROUND_HALF_UP);
        // 新的卖出冻结资金 = 委托卖出量 - 已成交量 - 本次成交量
        BigDecimal newSellLockVolume = sellOrder.getVolume().subtract(sellOrder.getDeal()).subtract(volume);
        this.modifyEntrustOrder(buyOrder, newBuyLockAmount, volume, buyVolume);
        this.modifyEntrustOrder(sellOrder, newSellLockVolume, volume, sellVolume);
        Map<String, Object> result = new HashMap<>(6);
        result.put(MATCH_RESULT_DEAL_ORDER, turnoverOrder);
        result.put(MATCH_RESULT_BUY_ORDER, buyOrder);
        result.put(MATCH_RESULT_SELL_ORDER, sellOrder);
        result.put(MATCH_RESULT_NEW_BUY_LOCK, newBuyLockAmount);
        result.put(MATCH_RESULT_NEW_SELL_LOCK, newSellLockVolume);
        result.put(MATCH_RESULT_PART_MATCH, false);
        // 未成交数量
        BigDecimal needDeal = entrustOrder.getVolume().subtract(entrustOrder.getDeal());
        if (needDeal.compareTo(volume) > 0) {
            result.put(MATCH_RESULT_PART_MATCH, true);
        }
        return result;
    }

    /**
     * 更新委托单状态
     *
     * @param entrustOrder   委托订单
     * @param lockAmount     最新冻结金额
     * @param volume         实际交易数量
     * @param canTradeVolume 可成交数量
     */
    private void modifyEntrustOrder(EntrustOrder entrustOrder, BigDecimal lockAmount, BigDecimal volume, BigDecimal canTradeVolume) {
        int count = 0;
        if (canTradeVolume.compareTo(volume) == 1) {
            // 部分成交，更新委托单状态
            count = entrustOrderService.modifyTradeEntrustOrder(entrustOrder.getId(), lockAmount, volume, OrderStatus.PENDING);
        } else {
            // 全部成交，更新委托单状态
            count = entrustOrderService.modifyTradeEntrustOrder(entrustOrder.getId(), BigDecimal.ZERO, volume, OrderStatus.DEAL);
        }
        if (count == 0) {
            throw new VolumeException("撮合失败：成交量异常");
        }
    }

}
